﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SilverTile;
using SilverTile.Sprites;
using SilverTile.ExtensionMethods;

namespace SilverTileTests
{

    [TestClass]
    public class AnimatedSpriteTests
    {

        [TestMethod]
        public void Animated_Sprite_Should_Have_Constructor_That_Takes_Several_Frames()
        {
            
            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");

            ISprite animatedSprite = new AnimatedSprite("Left", frame1, frame2);

            Assert.AreEqual(frame1.Width, animatedSprite.Width, "Animated sprite got incorrect width");
            Assert.AreEqual(frame1.Height, animatedSprite.Height, "Animated sprite got incorrect height");

        }

        [TestMethod]
        public void Update_Method_Should_Make_AnimatedSprite_Change_Frame()
        {

            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            frame2.Pixels[10] = Color.FromArgb(255, 255, 255, 0).ToInt32(); // Change a pixels color

            ISprite animatedSprite = new AnimatedSprite("Left", frame1, frame2)
            {
                Fps = 10 // Each frame lasts for 100 ms
            };

            CollectionAssert.AreEqual(frame1.Pixels, animatedSprite.Pixels, "Animated sprites pixels are not the same as frame 1's pixels");

            animatedSprite.Update(TimeSpan.FromMilliseconds(150));

            CollectionAssert.AreEqual(frame2.Pixels, animatedSprite.Pixels, "Animated sprites pixels were not changed to frame 2's pixels");

        }

        [TestMethod]
        public void Fps_Should_Affect_When_Frames_Are_Changed()
        {

            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            frame2.Pixels[10] = Color.FromArgb(255, 255, 255, 0).ToInt32(); // Change a pixels color

            ISprite animatedSprite = new AnimatedSprite("Left", frame1, frame2)
            {
                Fps = 10 // Each frame lasts for 100 ms
            };

            CollectionAssert.AreEqual(frame1.Pixels, animatedSprite.Pixels, "Animated sprites pixels are not the same as frame 1's pixels");

            animatedSprite.Update(TimeSpan.FromMilliseconds(50)); // Still at frame 1
            CollectionAssert.AreEqual(frame1.Pixels, animatedSprite.Pixels, "Animated sprites pixels are not the same as frame 1's pixels");
            
            animatedSprite.Update(TimeSpan.FromMilliseconds(150)); // At frame 2
            CollectionAssert.AreEqual(frame2.Pixels, animatedSprite.Pixels, "Animated sprites pixels were not changed to frame 2's pixels");

            animatedSprite.Update(TimeSpan.FromMilliseconds(100)); // Back at frame 1
            CollectionAssert.AreEqual(frame1.Pixels, animatedSprite.Pixels, "Animated sprites pixels are not the same as frame 1's pixels");

            animatedSprite.Update(TimeSpan.FromMilliseconds(100)); // Back at frame 2
            CollectionAssert.AreEqual(frame2.Pixels, animatedSprite.Pixels, "Animated sprites pixels are not the same as frame 2's pixels");
            
        }

        [TestMethod]
        public void Animated_Sprite_Should_Allow_Adding_Many_Animations()
        {
            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1, frame2);

            animatedSprite.AddAnimations("Right", frame2, frame1);
            
        }

        [TestMethod]
        public void Animated_Sprite_Should_Support_Switching_Of_Animations()
        {
            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1, frame2);

            animatedSprite.AddAnimations("Right", frame2, frame1);

            animatedSprite.SetActiveAnimation("Right");
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Animated_Sprite_Should_Throw_Exception_When_Selecting_An_Unknown_Animation()
        {
            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1, frame2);

            animatedSprite.AddAnimations("Right", frame2, frame1);

            animatedSprite.SetActiveAnimation("UnknownAnimation");
        }

        [TestMethod]
        public void Switching_Animation_Should_Change_The_Active_Frame()
        {

            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            frame1.Pixels[0] = 0;

            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            frame2.Pixels[0] = 999;

            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1);
            animatedSprite.AddAnimations("Right", frame2);

            animatedSprite.SetActiveAnimation("Right");

            CollectionAssert.AreEqual(frame2.Pixels, animatedSprite.Pixels, "Animated sprite did not switch to the animation named 'Right'");
            
        }

        [TestMethod]
        public void The_First_Animation_Should_Be_The_Active_Animation_Per_Default()
        {

            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");

            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            frame2.Pixels[0] = 999; // Modify frame2 so that we can distinguish it from frame1
 
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1);
            animatedSprite.AddAnimations("Right", frame2);

            Assert.AreSame(frame1.Pixels, animatedSprite.Pixels, "Frame1 should be the active frame");

        }

        [TestMethod]
        public void Animated_Sprite_Should_Get_Origin_From_The_Active_Animation()
        {
            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png") { OriginX = 10, OriginY = 20 };
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png") { OriginX = 15, OriginY = 25 };
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1);
            animatedSprite.AddAnimations("Right", frame2);

            Assert.AreEqual(frame1.OriginX, animatedSprite.OriginX, "Animated sprite does not have OriginX from its active animation (Left)");
            Assert.AreEqual(frame1.OriginY, animatedSprite.OriginY, "Animated sprite does not have OriginY from its active animation (Left)");

            animatedSprite.SetActiveAnimation("Right");
            Assert.AreEqual(frame2.OriginX, animatedSprite.OriginX, "Animated sprite does not have OriginX from its active animation (Right)");
            Assert.AreEqual(frame2.OriginY, animatedSprite.OriginY, "Animated sprite does not have OriginY from its active animation (Right)");

        }
        
        [TestMethod]
        public void Animated_Sprite_Should_Get_Origin_From_The_Active_Frame()
        {
            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png") { OriginX = 10, OriginY = 20 };
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png") { OriginX = 15, OriginY = 25 };
            AnimatedSprite animatedSprite = new AnimatedSprite("Animation", frame1, frame2) { Fps = 1 };

            Assert.AreEqual(frame1.OriginX, animatedSprite.OriginX, "Animated sprite does not have OriginX from its active frame (1)");
            Assert.AreEqual(frame1.OriginY, animatedSprite.OriginY, "Animated sprite does not have OriginY from its active frame (1)");

            animatedSprite.Update(TimeSpan.FromMilliseconds(1050));
            Assert.AreEqual(frame2.OriginX, animatedSprite.OriginX, "Animated sprite does not have OriginX from its active frame (2)");
            Assert.AreEqual(frame2.OriginY, animatedSprite.OriginY, "Animated sprite does not have OriginY from its active frame (2)");

        }
        
        [TestMethod]
        public void Animated_Sprite_Should_Get_Size_From_The_Active_Frame()
        {
            IFrame frame1 = new Frame(10, 20);
            IFrame frame2 = new Frame(30, 40);
            AnimatedSprite animatedSprite = new AnimatedSprite("Animation", frame1, frame2) { Fps = 1 };

            Assert.AreEqual(frame1.Width, animatedSprite.Width, "Animated sprite does not have Width from its active frame (1)");
            Assert.AreEqual(frame1.Height, animatedSprite.Height, "Animated sprite does not have Height from its active frame (1)");

            animatedSprite.Update(TimeSpan.FromMilliseconds(1050));
            Assert.AreEqual(frame2.Width, animatedSprite.Width, "Animated sprite does not have Width from its active frame (2)");
            Assert.AreEqual(frame2.Height, animatedSprite.Height, "Animated sprite does not have Height from its active frame (2)");

        }

        [TestMethod]
        public void Animated_Sprite_Should_Tell_Which_Animation_Is_Active()
        {

            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1, frame2);

            animatedSprite.AddAnimations("Right", frame2, frame1);
            animatedSprite.SetActiveAnimation("Right");

            Assert.AreEqual("Right", animatedSprite.ActiveAnimation, "Active animation is not 'Right'");

        }

        [TestMethod]
        public void Animated_Sprite_Should_Allow_A_Method_To_Be_Called_When_An_Animation_Is_Complete()
        {

            IFrame frame1 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            IFrame frame2 = new Frame("/SilverTileTests;component/Tiles/0002.png");
            AnimatedSprite animatedSprite = new AnimatedSprite("Left", frame1, frame2);

            bool animationComplete = false;

            animatedSprite.AddAnimations("Right", frame2, frame1);
            animatedSprite.SetActiveAnimation("Right", a => 
                {
                    animationComplete = true;
                    a.SetActiveAnimation("Left", an => animationComplete = false); // The next time the animation is complete the flag should be reset to false
                });

            animatedSprite.Update(TimeSpan.FromMilliseconds(110));
            animatedSprite.Update(TimeSpan.FromMilliseconds(110));
            Assert.IsTrue(animationComplete, "AnimationComplete method was not called the first time");

            animatedSprite.Update(TimeSpan.FromMilliseconds(110));
            animatedSprite.Update(TimeSpan.FromMilliseconds(110));
            Assert.IsFalse(animationComplete, "AnimationComplete method was not called the second time");

        }

    }
}
